/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

#include "meta/type_check.h"
#include "util.h"

using namespace metal;
using namespace metal::meta;
using namespace std;

namespace {
  struct x { };
  struct y { };

  typedef bindings<> empty_binding;
  typedef metal::meta::bind<x, float, empty_binding>::type x_bound;
  typedef metal::meta::bind<y, int, x_bound>::type yx_bound;
  typedef metal::meta::bind<x, int, yx_bound>::type xyx_bound;

  // identifiers

  static_assert(type_check<ident_expr<x>, empty_binding>::is_safe,
		"identifiers always safe");

  static_assert(!type_check<ident_expr<x>, empty_binding>::is_bound,
		"should have detected that x is not bound");

  static_assert(!type_check<ident_expr<y>, empty_binding>::is_bound,
		"should have detected that y is not bound");

  static_assert(type_check<ident_expr<x>, x_bound>::is_bound,
		"should have detected that x is bound");

  static_assert(!type_check<ident_expr<y>, x_bound>::is_bound,
		"should have detected that y is not bound");

  static_assert(type_check<ident_expr<x>, yx_bound>::is_bound,
		"should have detected that x is bound");

  static_assert(type_check<ident_expr<y>, yx_bound>::is_bound,
		"should have detected that y is bound");

  static_assert(type_check<ident_expr<x>, xyx_bound>::is_bound,
		"should have detected that x is bound");

  static_assert(type_check<ident_expr<y>, xyx_bound>::is_bound,
		"should have detected that y is bound");

  static_assert(is_same<float, type_check<ident_expr<x>, x_bound>::type>::value,
		"x is float");

  static_assert(is_same<float, type_check<ident_expr<x>, yx_bound>::type>::value,
		"x is float");

  static_assert(is_same<int, type_check<ident_expr<x>, xyx_bound>::type>::value,
		"x is now int");

  static_assert(is_same<int, type_check<ident_expr<y>, yx_bound>::type>::value,
		"y is int");

  static_assert(is_same<int, type_check<ident_expr<y>, xyx_bound>::type>::value,
		"y is int");

  // constants

  typedef constant_expr<float> f_const;
  typedef constant_expr<unsigned long> ul_const;
  typedef constant_expr<bool> b_const;
  typedef constant_expr<double> d_const;

  static_assert(type_check<b_const>::is_safe, "constant always safe");
  static_assert(type_check<f_const>::is_bound, "constant always bound");
  static_assert(type_check<ul_const>::is_bound, "constant always bound");

  static_assert(is_same<float, type_check<f_const>::type>::value,
		"float constant type");
  static_assert(is_same<unsigned long, type_check<ul_const>::type>::value,
		"ulong constant type");

  // unary operators

  typedef unary_expr<arithmatic_tag, f_const> negate;
  typedef unary_expr<integral_tag, ul_const> inot;
  typedef unary_expr<boolean_tag, b_const> bnot;

  static_assert(type_check<negate>::is_bound,
		"negating a constant should be bound");
  static_assert(type_check<inot>::is_bound,
		"bit-flipping a constant should be bound");
  static_assert(type_check<bnot>::is_bound,
		"not-ing a constant should be bound");
  static_assert(!type_check<unary_expr<arithmatic_tag, ident_expr<x>>, empty_binding>::is_bound,
		"not-ing an unbound identifier is unbound");

  static_assert(type_check<negate>::is_safe,
		"negating a constant should be safe");
  static_assert(type_check<inot>::is_safe,
		"bit-flipping a constant should be safe");
  static_assert(type_check<bnot>::is_safe,
		"not-ing a constant should be safe");

  static_assert(!type_check<unary_expr<boolean_tag, f_const>>::is_safe,
		"not-ing a float should not be safe");
  static_assert(!type_check<unary_expr<boolean_tag, ul_const>>::is_safe,
		"not-ing a int should not be safe");
  static_assert(!type_check<unary_expr<integral_tag, f_const>>::is_safe,
		"bit-flipping a float should not be safe");

  static_assert(is_same<float, type_check<negate>::type>::value,
		"negating a constant should retain type");
  static_assert(is_same<unsigned long, type_check<inot>::type>::value,
		"bit-flipping a constant should retain type");
  static_assert(is_same<bool, type_check<bnot>::type>::value,
		"not-ing a constant should retain type");

  // binary operators

  typedef binary_expr<boolean_tag, b_const, b_const> b_op;

  static_assert(!type_check<binary_expr<boolean_tag, ident_expr<x>, ident_expr<y>>, x_bound>::is_bound,
		"rhs is unbound");
  static_assert(!type_check<binary_expr<boolean_tag, ident_expr<y>, ident_expr<x>>, x_bound>::is_bound,
		"lhs is unbound");

  static_assert(type_check<b_op>::is_bound,
		"operator should combine bound flags");
  static_assert(type_check<b_op>::is_safe,
		"bool op should be safe");
  static_assert(is_same<bool, type_check<b_op>::type>::value,
		"bool op should evaluate to bool");

  typedef binary_expr<arithmatic_tag, f_const, f_const> flop;

  static_assert(type_check<flop>::is_bound,
		"operator should combine bound flags");
  static_assert(type_check<flop>::is_safe,
		"flop should be safe");
  static_assert(is_same<float, type_check<flop>::type>::value,
		"flop should evaluate to float");

  typedef binary_expr<integral_tag, ul_const, ul_const> i_op;

  static_assert(type_check<i_op>::is_bound,
		"operator should combine bound flags");
  static_assert(type_check<i_op>::is_safe,
		"int op should be safe");
  static_assert(is_same<unsigned long, type_check<i_op>::type>::value,
		"int op should evaluate to int");

  static_assert(!type_check<binary_expr<integral_tag, f_const, b_const>>::is_safe,
		"Should not allow un-typesafe integral operator on float operand");

  typedef constant_expr<short> s_const;
  typedef binary_expr<arithmatic_tag, f_const, s_const> promo_op;

  static_assert(is_same<float, promote<float, short>::type>::value,
		"checking promotion");
  static_assert(is_same<float, type_check<promo_op>::type>::value,
		"Did not promote to correct type");
  static_assert(type_check<promo_op>::is_safe,
		"Should allow float operator with promotable-to-float operand");

  typedef binary_expr<arithmatic_tag, s_const, f_const> promo_op_1;
  static_assert(is_same<float, promote<short, float>::type>::value,
		"checking promotion");
  static_assert(is_same<float, type_check<promo_op_1>::type>::value,
		"Did not promote to correct type");
  static_assert(type_check<promo_op_1>::is_safe,
		"Should allow float operator with promotable-to-float operand");

  typedef binary_expr<comparison_tag, f_const, f_const> ff_cmp;
  typedef binary_expr<comparison_tag, f_const, s_const> fs_cmp;
  typedef binary_expr<comparison_tag, ul_const, ul_const> ulul_cmp;

  static_assert(type_check<ff_cmp>::is_bound,
		"comparison should combine bound flags");
  static_assert(type_check<ff_cmp>::is_safe,
		"comparing two floats should be safe");
  static_assert(is_same<bool, type_check<ff_cmp>::type>::value,
		"comparison should be boolean");

  static_assert(type_check<fs_cmp>::is_bound,
		"comparison should combine bound flags");
  static_assert(type_check<fs_cmp>::is_safe,
		"comparing a float and a short should be safe");
  static_assert(is_same<bool, type_check<fs_cmp>::type>::value,
		"comparison should be boolean");

  static_assert(type_check<ulul_cmp>::is_bound,
		"comparison should combine bound flags");
  static_assert(type_check<ulul_cmp>::is_safe,
		"comparing two ulongs should be safe");
  static_assert(is_same<bool, type_check<ulul_cmp>::type>::value,
		"comparison should be boolean");

  typedef binary_expr<arithmatic_tag, ident_expr<x>, ident_expr<y> > xplusy;
  static_assert(type_check<xplusy, xyx_bound>::is_bound, "");
  static_assert(type_check<xplusy, xyx_bound>::l::is_bound, "");
  static_assert(type_check<xplusy, xyx_bound>::r::is_bound, "");
  static_assert(type_check<xplusy, xyx_bound>::is_safe, "");
  static_assert(is_same<int, type_check<xplusy, xyx_bound>::type>::value, "");

  // function call

  struct f {
  };

  struct g {
  };

  struct h {
  };

  typedef metal::meta::bind<f, list<float>, empty_binding>::type nullary_f;
  typedef metal::meta::bind<g, list<double, int>, nullary_f>::type nullary_f_unary_g;
  typedef metal::meta::bind<h, list<double, int, float >, nullary_f_unary_g>::type nullary_f_unary_g_binary_h;

  typedef type_check<call_expr<ident_expr<f> >, nullary_f> call_nullary_f;
  static_assert(call_nullary_f::is_bound,
		"f() bound");
  static_assert(call_nullary_f::is_safe,
		"f() safe");
  static_assert(call_nullary_f::correct_arg_count,
		"f() correct arg count");
  static_assert(is_same<float, call_nullary_f::type>::value,
		"type of f() is float");

  typedef type_check<call_expr<ident_expr<f> >, nullary_f_unary_g> call_nullary_f_2;
  static_assert(call_nullary_f_2::is_bound,
		"f() bound");
  static_assert(call_nullary_f_2::is_safe,
		"f() safe");
  static_assert(call_nullary_f_2::correct_arg_count,
		"f() correct arg count");
  static_assert(is_same<float, call_nullary_f_2::type>::value,
		"type of f() is float");

  typedef constant_expr<int> i_const;
  typedef type_check<call_expr<ident_expr<g>, i_const>, nullary_f_unary_g> call_unary_g;
  static_assert(call_unary_g::is_bound,
		"g(int) bound");
  static_assert(call_unary_g::is_safe,
		"g(int) safe");
  static_assert(call_unary_g::correct_arg_count,
		"g(int) correct arg count");
  static_assert(is_same<double, call_unary_g::type>::value,
		"type of g(int) is double");

  typedef type_check<call_expr<ident_expr<g>, i_const>, nullary_f_unary_g_binary_h> call_unary_g_2;

  static_assert(call_unary_g_2::is_bound,
		"g(int) bound");
  static_assert(call_unary_g_2::is_safe,
		"g(int) safe");
  static_assert(call_unary_g_2::correct_arg_count,
		"g(int) correct arg count");
  static_assert(is_same<double, call_unary_g_2::type>::value,
		"type of g(int) is double");

  typedef type_check<call_expr<ident_expr<h>, i_const, f_const>, nullary_f_unary_g_binary_h> call_binary_h;
  static_assert(call_binary_h::is_bound,
		"h(int, float) bound");
  static_assert(call_binary_h::is_safe,
		"h(int, float) safe");
  static_assert(call_binary_h::correct_arg_count,
		"g(int) correct arg count");
  static_assert(is_same<double, call_binary_h::type>::value,
		"type of h(int, float) is double");

  static_assert(!type_check<call_expr<ident_expr<h>, i_const, f_const>, nullary_f_unary_g>::is_bound,
		"h(int, float) not bound");

  static_assert(!type_check<call_expr<ident_expr<f>, i_const>, nullary_f>::correct_arg_count,
		"allowed to call f with too many args");
  static_assert(!type_check<call_expr<ident_expr<h>, i_const>, nullary_f_unary_g_binary_h>::correct_arg_count,
		"allowed to call f with too few args");
  static_assert(!type_check<call_expr<ident_expr<g>, f_const>, nullary_f_unary_g_binary_h>::is_safe,
		"allowed to call g with unsafe argument conversion");
  static_assert(type_check<call_expr<ident_expr<g>, s_const>, nullary_f_unary_g_binary_h>::is_safe,
		"allowed to call g with unsafe argument conversion");

  typedef meta::bind<g, list<int, int>, yx_bound>::type gyx_bound;
  typedef meta::bind<g, list<int, int>, x_bound>::type gx_bound;

  static_assert(type_check<call_expr<ident_expr<g>, ident_expr<y>>, gyx_bound>::is_bound,
		"could not call g(int) with bound arg");
  static_assert(!type_check<call_expr<ident_expr<g>, ident_expr<y>>, gx_bound>::is_bound,
		"allowed to call g(int) with unbound arg");

  // array construction expr

  static_assert(type_check<array_expr<f_const>>::is_safe,
		"[<flt>] is safe");
  static_assert(type_check<array_expr<f_const>>::is_safe,
		"[<flt>] is bound");
  static_assert(is_same<float*, type_check<array_expr<f_const>>::type>::value,
		"[<flt>] is float*");

  typedef array_expr<f_const, f_const> ff_a;
  static_assert(type_check<ff_a>::is_safe,
		"[<flt>, <flt>] is safe");
  static_assert(type_check<ff_a>::is_bound,
		"[<flt>, <flt>] is bound");
  static_assert(is_same<float*, type_check<ff_a>::type>::value,
		"[<flt>, <flt>] is float*");

  static_assert(is_same<float**, type_check<array_expr<ff_a>>::type>::value,
		"[[<flt>] is float**");

  typedef ident_expr<x> id_x;
  static_assert(!type_check<array_expr<id_x> >::is_bound,
		"should not allow [x] (free x)");
  static_assert(!type_check<array_expr<f_const, id_x>>::is_bound,
		"should not allow [<flt>, x] (free x)");

  typedef unary_expr<integral_tag, f_const> unsafe_op;
  static_assert(!type_check<array_expr<unsafe_op>>::is_safe,
		"should not allow [<unsafe>]");
  static_assert(!type_check<unsafe_op>::is_safe,
		"should not allow <unsafe>");

  static_assert(type_check<array_expr<f_const, s_const>>::is_safe,
		"should allow [<flt>, <short>]");
  static_assert(!type_check<array_expr<f_const, unsafe_op>>::is_safe,
		"should not allow [<flt>, <unsafe>]");
  static_assert(!type_check<array_expr<f_const, ul_const>>::is_safe,
		"should not allow [<flt>, <ulong>]");

  // eval

  typedef eval_expr<f_const> trivial_eval;

  static_assert(type_check<trivial_eval>::is_safe,
		"eval (<constant float>) with () is safe");
  static_assert(type_check<trivial_eval>::is_bound,
		"eval (<constant float>) with () is bound");
  static_assert(is_same<float, type_check<trivial_eval>::type>::value,
		"eval (<constant float>) with () evaluates to float");
  typedef eval_expr<id_x, binding<x, f_const>> eval_x_constant;

  static_assert(type_check<eval_x_constant>::is_safe,
		"eval (x) with (x=<constant float>) is safe");
  static_assert(type_check<eval_x_constant>::is_bound,
		"eval (x) with (x=<constant float>) is bound");
  static_assert(is_same<float, type_check<eval_x_constant>::type>::value,
		"eval (x) with (x=<constant float>) evaluates to float");

  typedef eval_expr<id_x, binding<x, id_x>> eval_x_selfref;

  static_assert(!type_check<eval_x_selfref>::is_bound,
		"eval (x) in (x=x) is not bound");

  // map

  typedef map_expr<id_x, binding<x, array_expr<f_const> > > x_in_singleton;

  static_assert(type_check<x_in_singleton>::is_safe,
		"{x : x in [<flt>]} is safe");
  static_assert(type_check<x_in_singleton>::is_bound,
		"{x : x in [<flt>]} is bound");
  static_assert(is_same<float*, type_check<x_in_singleton>::type>::value,
		"{x : x in [<flt>]} evaluates to float*");

  typedef map_expr<f_const, binding<x, array_expr<f_const>>, binding<y, array_expr<id_x>>> unsafe_map;
  static_assert(!type_check<unsafe_map>::is_bound,
		"{<flt> : x in [<flt>] ; y in [x]} is not bound");

  typedef ident_expr<y> id_y;
  typedef map_expr<binary_expr<arithmatic_tag, id_x, id_y>, binding<x, array_expr<f_const>>, binding<y, array_expr<f_const>>> x_y_in_singletons;

  static_assert(type_check<x_y_in_singletons>::is_safe,
		"{x + y : x in [<flt>] ; y in [<flt>]} is safe");
  static_assert(type_check<x_y_in_singletons>::is_bound,
		"{x + y : x in [<flt>] ; y in [<flt>]} is bound");
  static_assert(is_same<float*, type_check<x_y_in_singletons>::type>::value,
		"{x + y : x in [<flt>] ; y in [<flt>]} evaluates to float*");

  // conditionals

  typedef cond_expr<binary_expr<comparison_tag, f_const, s_const>, f_const, f_const> simple_cond;

  static_assert(type_check<simple_cond>::is_safe,
		"if (<flt> < <flt>) then <flt> else <flt> is safe");
  static_assert(type_check<simple_cond>::is_bound,
		"if (<flt> < <flt>) then <flt> else <flt> is bound");
  static_assert(is_same<float, type_check<simple_cond>::type>::value,
		"if (<flt> < <flt>) then <flt> else <flt> is float");

  static_assert(!type_check<cond_expr<constant_expr<int>, f_const, f_const>>::is_safe,
		"conditional must be boolean");
  static_assert(!type_check<cond_expr<constant_expr<bool>, f_const, ul_const>>::is_safe,
		"then/else must be of same type");
  static_assert(!type_check<cond_expr<constant_expr<bool>, f_const, s_const>>::is_safe,
		"then/else type promotion not allowed");

  // filters

  typedef filter_expr<ident_expr<x>, 
		      x,
		      array_expr<f_const, f_const>,
		      b_const
		      > simple_filter;

  static_assert(type_check<simple_filter>::is_safe,
		"{x in [<flt>, <flt>] | <bool>} is safe");
  static_assert(type_check<simple_filter>::is_bound,
		"{x in [<flt>, <flt>] | <bool>} is bound");
  static_assert(is_same<float*, type_check<simple_filter>::type>::value,
		"{x in [<flt>, <flt>] | <bool>} is float*");

typedef filter_expr<ident_expr<x>, 
		    x, 
		    array_expr<id_x, id_x>, 
		    binary_expr<comparison_tag, id_x, f_const>
		    > cmp_filter;

  static_assert(type_check<cmp_filter, x_bound>::is_safe,
		"{x in [<flt>, <flt>] | <bool>} is safe");
  static_assert(type_check<cmp_filter, x_bound>::is_bound,
		"{x in [<flt>, <flt>] | <bool>} is bound");
  static_assert(is_same<float*, type_check<cmp_filter, x_bound>::type>::value,
		"{x in [<flt>, <flt>] | <bool>} is float*");

  typedef filter_expr<
    binary_expr<arithmatic_tag, id_x, d_const>,
    x, 
    array_expr<f_const>, 
    b_const
    > promoting_filter;

  static_assert(type_check<promoting_filter, x_bound>::is_safe,
		"{x * <dbl> : x in [<flt>] | <bool>} is safe");
  static_assert(type_check<promoting_filter, x_bound>::is_bound,
		"{x * <dbl> : x in [<flt>] | <bool>} is bound");
  static_assert(is_same<double*, type_check<promoting_filter, x_bound>::type>::value,
		"{x * <dbl> : x in [<flt>] | <bool>} is double*");

  static_assert(!type_check<filter_expr<ident_expr<x>, x, array_expr<f_const>, binary_expr<arithmatic_tag, id_x, id_x>>>::is_safe,
		"should not allow non-boolean filter");
		static_assert(!type_check<filter_expr<ident_expr<y>, y, array_expr<f_const>, id_x>>::is_bound,
		"shoud not allow unbound filter");
static_assert(!type_check<filter_expr<ident_expr<x>, x, array_expr<f_const>, binary_expr<arithmatic_tag, id_x, ul_const>>>::is_safe,
		"shoud not allow unsafe filter");
  static_assert(!type_check<filter_expr<y, array_expr<f_const>, b_const, id_x>>::is_bound,
		"should not allow unbound expression");
  static_assert(!type_check<filter_expr<x, array_expr<f_const>, b_const, binary_expr<arithmatic_tag, id_x, ul_const>>>::is_safe,
		"should not allow unsafe expression");

  // array index

  typedef subscript_expr<array_expr<f_const>, ul_const> simple_array;

  static_assert(type_check<simple_array>::is_safe,
		"[<flt>][0] is safe");
  static_assert(type_check<simple_array>::is_bound,
		"[<flt>][0] is bound");
  static_assert(is_same<float, type_check<simple_array>::type>::value,
		"[<flt>][0] is float");

  static_assert(!type_check<subscript_expr<array_expr<f_const, ul_const>, ul_const>>::is_safe,
		"[<flt>, <ulong>][0] is not safe");
  static_assert(!type_check<subscript_expr<array_expr<id_x>, ul_const>>::is_bound,
		"[x][0] is not bound");
  static_assert(!type_check<subscript_expr<array_expr<f_const>, binary_expr<arithmatic_tag, f_const, ul_const>>>::is_safe,
		"[<flt>][<flt> + <ulong>] is not safe");
  static_assert(!type_check<subscript_expr<array_expr<ul_const>, id_x>>::is_bound,
		"[<ulong>][x] is not bound");
  static_assert(!type_check<subscript_expr<array_expr<ul_const>, f_const>>::is_safe,
		"[<ulong>][<float>] uses illegal index type");

  typedef type_check<subscript_expr<id_x, id_y>, bindings<binding<x, float*>, binding<y, unsigned>>> bound2_check;
  static_assert(bound2_check::is_safe,
		"x[y] is safe");
  static_assert(bound2_check::is_bound,
		"x[y] is bound");
  static_assert(is_same<float, bound2_check::type>::value,
		"x[y] is float");

  static_assert(bound2_check::a_check::is_safe, "");
  static_assert(bound2_check::a_check::is_bound, "");

  static_assert(bound2_check::i_check::is_safe, "");
  static_assert(bound2_check::i_check::is_bound, "");

  // structure member expression

  typedef structure<binding<x, float>, binding<y, bool> > xy_struct;

  struct s { };
  typedef metal::meta::bind<s, xy_struct, empty_binding>::type s_bound;

  typedef dot_expr<ident_expr<s>, x> dot_x;
  typedef dot_expr<ident_expr<s>, y> dot_y;
  typedef dot_expr<ident_expr<s>, s> dot_s;

  static_assert(type_check<dot_x, s_bound>::is_safe,
		"s.x is safe");
  static_assert(type_check<dot_x, s_bound>::is_bound,
		"s.x is bound");
  static_assert(is_same<float, type_check<dot_x, s_bound>::type>::value,
		"s.x is <float>");

  static_assert(type_check<dot_y, s_bound>::is_safe,
		"s.y is safe");
  static_assert(type_check<dot_y, s_bound>::is_bound,
		"s.y is bound");
  static_assert(is_same<bool, type_check<dot_y, s_bound>::type>::value,
		"s.y is <bool>");

  static_assert(!type_check<dot_s, s_bound>::is_bound,
		"s.s is not bound");

  // structure construction
  typedef cons_expr<xy_struct,
		    binding<x, id_x>, 
		    binding<y, binary_expr<comparison_tag, 
					   s_const, 
					   s_const
					   >
			    >
  > cons_s;

  typedef type_check<cons_s, x_bound> foo;
  static_assert(is_same<foo::next_check::next_check, type_check<cons_expr<xy_struct>, x_bound> >::value, "");
  
  static_assert(foo::next_check::next_check::is_safe, "");
  static_assert(foo::next_check::next_check::is_bound, "");
  
  static_assert(foo::next_check::is_safe, "");
  static_assert(foo::next_check::is_bound, "");
  
  static_assert(type_check<cons_s, x_bound>::is_safe,
		"new xy_struct { x=x, y=(<flt> < <short>) } is safe");
  static_assert(type_check<cons_s, x_bound>::is_bound,
		"new xy_struct { x=x, y=(<flt> < <short>) } is bound");
  static_assert(is_same<xy_struct, type_check<cons_s, x_bound>::type>::value,
		"new xy_struct { x=x, y=(<flt> < <short>) } is xy_struct");

  // casting
  typedef cast_expr<short, ident_expr<x> > safe_cast;

  static_assert(type_check<safe_cast, x_bound>::is_safe,
		"(short)x is safe");
  static_assert(type_check<safe_cast, x_bound>::is_bound,
		"(short)x is bound");
  static_assert(is_same<short, type_check<safe_cast, x_bound>::type>::value,
		"(short)x is short");

  // concatenation
  typedef binary_expr<arithmatic_tag, array_expr<constant_expr<int> >, array_expr<constant_expr<int> > > concat_expr;

#warning static_assert(type_check<concat_expr>::is_safe, "");
#warning static_assert(type_check<concat_expr>::is_bound, "");
#warning static_assert(is_same<int*, type_check<concat_expr>::type>::value, "");
}
